package com.epoint.nio.socket2.write;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class ServerPro {
    public static void main(String[] args) throws Exception {
        try(
            // 2.创建服务器
            ServerSocketChannel server = ServerSocketChannel.open()
        ){
            // 2.1绑定监听端口
            server.bind(new InetSocketAddress(8080));
            // 2.2设置非阻塞
            server.configureBlocking(false);

            // 创建Select
            Selector selector = Selector.open();
            server.register(selector, SelectionKey.OP_ACCEPT);

            /**
             * 4.一直循环接受客户端
             *
             */
            while (true){
                selector.select();

                // 获取所有事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                // 因为下面会涉及到循环里进行操作，故这里直接获取迭代器
                Iterator<SelectionKey> iterator = selectionKeys.iterator();

                while (iterator.hasNext()){
                    // 事件
                    SelectionKey key = iterator.next();

                    // 根据事件类型进行判断
                    if(key.isAcceptable()){
                        // 获取对应的通道
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                        // 客户端
                        SocketChannel accept = channel.accept();
                        if(accept != null){
                            System.out.println("有一个客户端连接" + accept);
                            // 设置非阻塞
                            accept.configureBlocking(false);

                            // 为该客户端绑定读事件
                            SelectionKey acceptKey = accept.register(selector, SelectionKey.OP_READ);

                            // 模拟传输一个很大的文件
                            StringBuilder sb = new StringBuilder();
                            for (int i = 0; i < 20000000; i++) {
                                sb.append("j");
                            }

                            // 1.开辟一个缓存buffer
                            ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                            // 将buffer的数据写进通道里，不过一次性肯定写不下，没事，后续在慢慢写
                            int write = accept.write(buffer);
                            System.out.println("第一个写入字节：" + write);

                            /**
                             * 如果上面没写完，那么我就注册一下事件，然后由 selector 去监听，触发事件后，再慢慢写，直到写完
                             *
                             * 说到底是，是用selector的多次监听+调用，来替换之前的while(buffer.hasRemaining())
                             *
                             * 好处在于，原来的while循环里经常做一些“无用功”，而selector监听+调用 不会做无用功
                             */
                            if(buffer.hasRemaining()){
                                // read 1  write 4
                                // 在原有关注事件的基础上，多关注 写事件
                                acceptKey.interestOps(acceptKey.interestOps() + SelectionKey.OP_WRITE);
                                // 把 buffer 作为附件加入 acceptKey
                                acceptKey.attach(buffer);
                            }
                        }
                    }else if(key.isWritable()){ // 如果是写事件
                        ByteBuffer attachment = (ByteBuffer) key.attachment();
                        SocketChannel channel = (SocketChannel) key.channel();

                        // 不要再出现while了，这样还是和之前的版本一个样子
//                        while(attachment.hasRemaining()){
//                            int write = channel.write(attachment);
//                            System.out.println(write);
//                        }
                        /**
                         * 要充分明白一次写入，然后多次监听、调用，直到数据写完
                         * 数据会一直在通道里，他不会丢失，只要通道里有数据，他会一直触发 write 事件，然后过来执行这个代码
                         */
                        int write = channel.write(attachment);
                        System.out.println("write事件写入字节：" + write);
                        if (!attachment.hasRemaining()) { // 写完了
                            key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                            key.attach(null);
                        }
                    }
                    iterator.remove();
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
